using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Kopernikus.Parsing.GenericActions;

namespace Kopernikus.Parsing
{
    public class Production : IEquatable<Production>
    {
        private static readonly Regex m_reLHS = new Regex(@"^\s*(\w+)\s*((\-\>)?)\s*");
        private static readonly Regex m_reRHS = new Regex(@"\s*(\S+)\s*");

        private readonly int m_hashCode;
        private readonly Symbol m_lhs;
        private readonly Symbol[] m_rhs;

        public Production(params string[] symbols)
            : this(Array.ConvertAll<String, Symbol>(symbols, delegate(string symbol) { return new Symbol(symbol); }))
        {
        }

        public Production(IEnumerable<Symbol> symbols)
        {
            if (symbols == null)
            {
                throw new ArgumentNullException("symbols");
            }

            List<Symbol> l = new List<Symbol>(symbols);
            if (l.Count == 0)
            {
                throw new ArgumentException("A production must be constructed from at least one symbol.", "symbols");
            }
            m_hashCode = 0;
            foreach (Symbol s in l)
            {
                m_hashCode ^= s.GetHashCode();
            }

            m_lhs = l[0];
            l.RemoveAt(0);
            m_rhs = l.ToArray();
        }

        public Symbol LHS
        {
            get { return m_lhs; }
        }

        public Symbol[] RHS
        {
            get { return m_rhs; }
        }

        #region Object overrides

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(m_lhs);
            sb.Append("->");
            sb.Append(
                string.Join(" ",
                            Array.ConvertAll<Symbol, String>(m_rhs, delegate(Symbol s) { return s.ToString(); })
                    ));
            return sb.ToString();
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as Production);
        }

        public override int GetHashCode()
        {
            return m_hashCode;
        }

        #endregion

        #region IEquatable<Production> Members

        public bool Equals(Production other)
        {
            if (other == null)
            {
                return false;
            }
            if (!m_lhs.Equals(other.m_lhs))
            {
                return false;
            }
            if (!m_rhs.Length.Equals(other.m_rhs.Length))
            {
                return false;
            }
            for (int i = 0; i < m_rhs.Length; ++i)
            {
                if (!m_rhs[i].Equals(other.m_rhs[i]))
                {
                    return false;
                }
            }
            return true;
        }

        #endregion

        public static Production Parse(string s)
        {
            return Parse(s, null);
        }

        public static Production Parse(string s, out ISemanticActionParametersFixup fixup)
        {
            SemanticActionParametersFixupBuilder semanticActionParametersFixupBuilder =
                new SemanticActionParametersFixupBuilder();

            Production p = Parse(s, semanticActionParametersFixupBuilder);
            fixup = semanticActionParametersFixupBuilder.CreateFixup(p);
            return p;
        }

        private static Production Parse(string s,
                                        SemanticActionParametersFixupBuilder semanticActionParametersFixupBuilder)
        {
            Match lhsMatch = m_reLHS.Match(s);
            if (!lhsMatch.Success)
            {
                throw new InvalidProductionException(s);
            }

            List<Symbol> symbols = new List<Symbol>();
            symbols.Add(new Symbol(lhsMatch.Groups[1].Value));

            int rhsSymbolIndex = 0;
            List<int> parameterReorderings = new List<int>();
            MatchCollection rhsMatches = m_reRHS.Matches(s.Substring(lhsMatch.Length));
            foreach (Match rhsMatch in rhsMatches)
            {
                string rawSymbol = rhsMatch.Groups[1].Value;
                string parameterSymbol;
                int parameterIndex;
                if (TrySplitRHSIntoSymbolAndParameterIndex(rawSymbol, out parameterSymbol, out parameterIndex))
                {
                    if (semanticActionParametersFixupBuilder != null)
                    {
                        semanticActionParametersFixupBuilder.MapParameterFromRhsSymbol(parameterIndex, rhsSymbolIndex);
                    }
                    symbols.Add(new Symbol(parameterSymbol));
                }
                else
                {
                    symbols.Add(new Symbol(rawSymbol));
                }
                ++rhsSymbolIndex;
            }
            return new Production(symbols);
        }


        private static bool TrySplitRHSIntoSymbolAndParameterIndex(string rawSymbol, out string symbol, out int index)
        {
            int n = rawSymbol.LastIndexOf('.');
            if (n > 0)
            {
                if (int.TryParse(rawSymbol.Substring(n + 1), out index))
                {
                    symbol = rawSymbol.Substring(0, n);
                    return true;
                }
            }
            index = 0;
            symbol = string.Empty;
            return false;
        }
    }
}